import 'reflect-metadata';
import { Logger } from './logger';
import express, { type Express, type Request, type Response, type NextFunction } from 'express';
import type { HttpMethod, ParamMetadata } from '../types';
import path from 'path';
import { AppService } from '../../app.service';

export class NestApplication {
  // 私有化一个Express实例
  private readonly app: Express = express();

  constructor(protected readonly module: any) {
    this.app.use(express.json());
    this.app.use(express.urlencoded({ extended: true }));
  }

  use(middleware: any) {
    this.app.use(middleware);
  }

  private resolveDependencies(controller: any) {
    // 取出控制器的依赖注入元数据
    // const injectedTokens = Reflect.getMetadata('injectedTokens', controller) || [];
    // 取出控制器的构造函数参数类型元数据
    // design:paramtypes -用于获取类构造函数参数的类型信息
    const constructorParams = Reflect.getMetadata('design:paramtypes', controller) || [];
    console.log(constructorParams, 'constructorParams');
    return constructorParams?.map((params: any, index: number) => {
      if (index === 0) {
        return new AppService();
      }
    });
  }

  // 初始化工作
  async init() {
    // 取出模块里所有的控制器，然后做好路由配置
    const controllers = Reflect.getMetadata('controllers', this.module) || [];
    Logger.log(`AppModule dependencies initializing...`, 'InstanceLoader');
    for (const controller of controllers) {
      // 解析出控制器的依赖
      const dependencies = this.resolveDependencies(controller);
      // 创建每个控制器的实例
      const instance = new controller(...dependencies);
      // 获取控制器的前缀
      const prefix: string = Reflect.getMetadata('prefix', controller) || '/';
      Logger.log(`${controller.name} ${prefix}`, 'RoutesResolver');
      const controllerPrototype = controller.prototype;
      // 遍历类原型上的方法名
      for (const methodName of Object.getOwnPropertyNames(controllerPrototype)) {
        const method = controllerPrototype[methodName];
        // 获取方法上的装饰器的方法名元数据
        const httpMethod: HttpMethod = Reflect.getMetadata('method', method);
        // 获取方法上的装饰器的路径元数据
        const pathMetadata: string = Reflect.getMetadata('path', method);
        const redirectUrl: string = Reflect.getMetadata('redirectUrl', method);
        const redirectStatusCode: number = Reflect.getMetadata('redirectStatusCode', method);
        const httpCode: number = Reflect.getMetadata('httpCode', method);
        const headers: { key: string; value: string }[] =
          Reflect.getMetadata('header', method) || [];
        if (!httpMethod) continue;
        const routePath = path.posix.join('/', prefix, pathMetadata);
        // 注册路由
        this.app[httpMethod.toLowerCase() as HttpMethod](
          routePath,
          (req: Request, res: Response, next: NextFunction) => {
            const args = this.resolveParams(instance, methodName, req, res, next);
            const result = method.call(instance, ...args);
            if (redirectUrl) {
              res.redirect(redirectStatusCode || 302, redirectUrl);
              return;
            }
            if (httpCode) {
              res.statusCode = httpCode;
            }
            const responseMetadata: ParamMetadata | undefined = this.getResponseMetadata(
              instance,
              methodName,
            );
            if (
              !responseMetadata ||
              (responseMetadata.data &&
                typeof responseMetadata.data === 'object' &&
                'passthrough' in responseMetadata.data &&
                responseMetadata.data.passthrough)
            ) {
              headers.filter(Boolean).forEach((header) => {
                res.setHeader(header.key, header.value);
              });
              res.send(result);
            }
          },
        );
        Logger.log(`Mapped${routePath} ${httpMethod} route`, 'RoutesResolver');
      }
      Logger.log(`Nest application successfully started`, 'NestApplication');
    }
  }

  private getResponseMetadata(controllerPrototype: any, methodName: string) {
    const paramsMetadata: Array<ParamMetadata> = Reflect.getMetadata(
      'params',
      controllerPrototype,
      methodName,
    );
    return paramsMetadata
      .filter(Boolean)
      .find(
        (params: ParamMetadata) =>
          params.key === 'response' || params.key === 'res' || params.key === 'next',
      );
  }

  private isValidKey(key: unknown): key is string | number {
    return typeof key === 'string' || typeof key === 'number';
  }

  private resolveParams(
    controllerPrototype: any,
    methodName: string,
    req: Request,
    res: Response,
    next: NextFunction,
  ) {
    const paramsMetadata = Reflect.getMetadata('params', controllerPrototype, methodName) || [];
    return paramsMetadata.map((params: ParamMetadata) => {
      const { key, data } = params;
      switch (key) {
        case 'request':
        case 'req':
          return req;
        case 'query':
          return this.isValidKey(data) ? req.query[data] : req.query;
        case 'session':
          return this.isValidKey(data) ? req.session[data] : req.session;
        case 'params':
          return this.isValidKey(data) ? req.params[data] : req.params;
        case 'body':
          return this.isValidKey(data) ? req.body[data] : req.body;
        case 'response':
        case 'res':
          return res;
        case 'next':
          return next;
        default:
          return null;
      }
    });
  }

  // 启动HTTP服务器
  async listen(port: number | string) {
    await this.init();
    this.app.listen(port, () => {
      Logger.log(`Application started on port ${port}...`, 'NestApplication');
    });
  }
}
